/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxPopupMenu
 *
 * Basic popup menu. To add a vertical scrollbar to a given submenu, the
 * following code can be used.
 *
 * (code)
 * var mxPopupMenuShowMenu = mxPopupMenu.prototype.showMenu;
 * mxPopupMenu.prototype.showMenu = function()
 * {
 *   mxPopupMenuShowMenu.apply(this, arguments);
 *
 *   this.div.style.overflowY = 'auto';
 *   this.div.style.overflowX = 'hidden';
 *   this.div.style.maxHeight = '160px';
 * };
 * (end)
 *
 * Constructor: mxPopupMenu
 *
 * Constructs a popupmenu.
 *
 * Event: mxEvent.SHOW
 *
 * Fires after the menu has been shown in <popup>.
 */
function mxPopupMenu(factoryMethod)
{
    this.factoryMethod = factoryMethod;

    if (factoryMethod != null)
    {
        this.init();
    }
};

/**
 * Extends mxEventSource.
 */
mxPopupMenu.prototype = new mxEventSource();
mxPopupMenu.prototype.constructor = mxPopupMenu;

/**
 * Variable: submenuImage
 *
 * URL of the image to be used for the submenu icon.
 */
mxPopupMenu.prototype.submenuImage = mxClient.imageBasePath + '/submenu.gif';

/**
 * Variable: zIndex
 *
 * Specifies the zIndex for the popupmenu and its shadow. Default is 10006.
 */
mxPopupMenu.prototype.zIndex = 10006;

/**
 * Variable: factoryMethod
 *
 * Function that is used to create the popup menu. The function takes the
 * current panning handler, the <mxCell> under the mouse and the mouse
 * event that triggered the call as arguments.
 */
mxPopupMenu.prototype.factoryMethod = null;

/**
 * Variable: useLeftButtonForPopup
 *
 * Specifies if popupmenus should be activated by clicking the left mouse
 * button. Default is false.
 */
mxPopupMenu.prototype.useLeftButtonForPopup = false;

/**
 * Variable: enabled
 *
 * Specifies if events are handled. Default is true.
 */
mxPopupMenu.prototype.enabled = true;

/**
 * Variable: itemCount
 *
 * Contains the number of times <addItem> has been called for a new menu.
 */
mxPopupMenu.prototype.itemCount = 0;

/**
 * Variable: autoExpand
 *
 * Specifies if submenus should be expanded on mouseover. Default is false.
 */
mxPopupMenu.prototype.autoExpand = false;

/**
 * Variable: smartSeparators
 *
 * Specifies if separators should only be added if a menu item follows them.
 * Default is false.
 */
mxPopupMenu.prototype.smartSeparators = false;

/**
 * Variable: labels
 *
 * Specifies if any labels should be visible. Default is true.
 */
mxPopupMenu.prototype.labels = true;

/**
 * Function: init
 *
 * Initializes the shapes required for this vertex handler.
 */
mxPopupMenu.prototype.init = function()
{
    // Adds the inner table
    this.table = document.createElement('table');
    this.table.className = 'mxPopupMenu';

    this.tbody = document.createElement('tbody');
    this.table.appendChild(this.tbody);

    // Adds the outer div
    this.div = document.createElement('div');
    this.div.className = 'mxPopupMenu';
    this.div.style.display = 'inline';
    this.div.style.zIndex = this.zIndex;
    this.div.appendChild(this.table);

    // Disables the context menu on the outer div
    mxEvent.disableContextMenu(this.div);
};

/**
 * Function: isEnabled
 *
 * Returns true if events are handled. This implementation
 * returns <enabled>.
 */
mxPopupMenu.prototype.isEnabled = function()
{
    return this.enabled;
};

/**
 * Function: setEnabled
 *
 * Enables or disables event handling. This implementation
 * updates <enabled>.
 */
mxPopupMenu.prototype.setEnabled = function(enabled)
{
    this.enabled = enabled;
};

/**
 * Function: isPopupTrigger
 *
 * Returns true if the given event is a popupmenu trigger for the optional
 * given cell.
 *
 * Parameters:
 *
 * me - <mxMouseEvent> that represents the mouse event.
 */
mxPopupMenu.prototype.isPopupTrigger = function(me)
{
    return me.isPopupTrigger() || (this.useLeftButtonForPopup && mxEvent.isLeftMouseButton(me.getEvent()));
};

/**
 * Function: addItem
 *
 * Adds the given item to the given parent item. If no parent item is specified
 * then the item is added to the top-level menu. The return value may be used
 * as the parent argument, ie. as a submenu item. The return value is the table
 * row that represents the item.
 *
 * Paramters:
 *
 * title - String that represents the title of the menu item.
 * image - Optional URL for the image icon.
 * funct - Function associated that takes a mouseup or touchend event.
 * parent - Optional item returned by <addItem>.
 * iconCls - Optional string that represents the CSS class for the image icon.
 * IconsCls is ignored if image is given.
 * enabled - Optional boolean indicating if the item is enabled. Default is true.
 * active - Optional boolean indicating if the menu should implement any event handling.
 * Default is true.
 */
mxPopupMenu.prototype.addItem = function(title, image, funct, parent, iconCls, enabled, active)
{
    parent = parent || this;
    this.itemCount++;

    // Smart separators only added if element contains items
    if (parent.willAddSeparator)
    {
        if (parent.containsItems)
        {
            this.addSeparator(parent, true);
        }

        parent.willAddSeparator = false;
    }

    parent.containsItems = true;
    var tr = document.createElement('tr');
    tr.className = 'mxPopupMenuItem';
    var col1 = document.createElement('td');
    col1.className = 'mxPopupMenuIcon';

    // Adds the given image into the first column
    if (image != null)
    {
        var img = document.createElement('img');
        img.src = image;
        col1.appendChild(img);
    }
    else if (iconCls != null)
    {
        var div = document.createElement('div');
        div.className = iconCls;
        col1.appendChild(div);
    }

    tr.appendChild(col1);

    if (this.labels)
    {
        var col2 = document.createElement('td');
        col2.className = 'mxPopupMenuItem' +
            ((enabled != null && !enabled) ? ' mxDisabled' : '');

        mxUtils.write(col2, title);
        col2.align = 'left';
        tr.appendChild(col2);

        var col3 = document.createElement('td');
        col3.className = 'mxPopupMenuItem' +
            ((enabled != null && !enabled) ? ' mxDisabled' : '');
        col3.style.paddingRight = '6px';
        col3.style.textAlign = 'right';

        tr.appendChild(col3);

        if (parent.div == null)
        {
            this.createSubmenu(parent);
        }
    }

    parent.tbody.appendChild(tr);

    if (active != false && enabled != false)
    {
        var currentSelection = null;

        mxEvent.addGestureListeners(tr,
            mxUtils.bind(this, function(evt)
            {
                this.eventReceiver = tr;

                if (parent.activeRow != tr && parent.activeRow != parent)
                {
                    if (parent.activeRow != null && parent.activeRow.div.parentNode != null)
                    {
                        this.hideSubmenu(parent);
                    }

                    if (tr.div != null)
                    {
                        this.showSubmenu(parent, tr);
                        parent.activeRow = tr;
                    }
                }

                // Workaround for lost current selection in page because of focus in IE
                if (document.selection != null && (mxClient.IS_QUIRKS || document.documentMode == 8))
                {
                    currentSelection = document.selection.createRange();
                }

                mxEvent.consume(evt);
            }),
            mxUtils.bind(this, function(evt)
            {
                if (parent.activeRow != tr && parent.activeRow != parent)
                {
                    if (parent.activeRow != null && parent.activeRow.div.parentNode != null)
                    {
                        this.hideSubmenu(parent);
                    }

                    if (this.autoExpand && tr.div != null)
                    {
                        this.showSubmenu(parent, tr);
                        parent.activeRow = tr;
                    }
                }

                // Sets hover style because TR in IE doesn't have hover
                tr.className = 'mxPopupMenuItemHover';
            }),
            mxUtils.bind(this, function(evt)
            {
                // EventReceiver avoids clicks on a submenu item
                // which has just been shown in the mousedown
                if (this.eventReceiver == tr)
                {
                    if (parent.activeRow != tr)
                    {
                        this.hideMenu();
                    }

                    // Workaround for lost current selection in page because of focus in IE
                    if (currentSelection != null)
                    {
                        // Workaround for "unspecified error" in IE8 standards
                        try
                        {
                            currentSelection.select();
                        }
                        catch (e)
                        {
                            // ignore
                        }

                        currentSelection = null;
                    }

                    if (funct != null)
                    {
                        funct(evt);
                    }
                }

                this.eventReceiver = null;
                mxEvent.consume(evt);
            })
        );

        // Resets hover style because TR in IE doesn't have hover
        mxEvent.addListener(tr, 'mouseout',
            mxUtils.bind(this, function(evt)
            {
                tr.className = 'mxPopupMenuItem';
            })
        );
    }

    return tr;
};

/**
 * Adds a checkmark to the given menuitem.
 */
mxPopupMenu.prototype.addCheckmark = function(item, img)
{
    var td = item.firstChild.nextSibling;
    td.style.backgroundImage = 'url(\'' + img + '\')';
    td.style.backgroundRepeat = 'no-repeat';
    td.style.backgroundPosition = '2px 50%';
};

/**
 * Function: createSubmenu
 *
 * Creates the nodes required to add submenu items inside the given parent
 * item. This is called in <addItem> if a parent item is used for the first
 * time. This adds various DOM nodes and a <submenuImage> to the parent.
 *
 * Parameters:
 *
 * parent - An item returned by <addItem>.
 */
mxPopupMenu.prototype.createSubmenu = function(parent)
{
    parent.table = document.createElement('table');
    parent.table.className = 'mxPopupMenu';

    parent.tbody = document.createElement('tbody');
    parent.table.appendChild(parent.tbody);

    parent.div = document.createElement('div');
    parent.div.className = 'mxPopupMenu';

    parent.div.style.position = 'absolute';
    parent.div.style.display = 'inline';
    parent.div.style.zIndex = this.zIndex;

    parent.div.appendChild(parent.table);

    var img = document.createElement('img');
    img.setAttribute('src', this.submenuImage);

    // Last column of the submenu item in the parent menu
    td = parent.firstChild.nextSibling.nextSibling;
    td.appendChild(img);
};

/**
 * Function: showSubmenu
 *
 * Shows the submenu inside the given parent row.
 */
mxPopupMenu.prototype.showSubmenu = function(parent, row)
{
    if (row.div != null)
    {
        row.div.style.left = (parent.div.offsetLeft +
            row.offsetLeft+row.offsetWidth - 1) + 'px';
        row.div.style.top = (parent.div.offsetTop+row.offsetTop) + 'px';
        document.body.appendChild(row.div);

        // Moves the submenu to the left side if there is no space
        var left = parseInt(row.div.offsetLeft);
        var width = parseInt(row.div.offsetWidth);
        var offset = mxUtils.getDocumentScrollOrigin(document);

        var b = document.body;
        var d = document.documentElement;

        var right = offset.x + (b.clientWidth || d.clientWidth);

        if (left + width > right)
        {
            row.div.style.left = Math.max(0, (parent.div.offsetLeft - width + ((mxClient.IS_IE) ? 6 : -6))) + 'px';
        }

        mxUtils.fit(row.div);
    }
};

/**
 * Function: addSeparator
 *
 * Adds a horizontal separator in the given parent item or the top-level menu
 * if no parent is specified.
 *
 * Parameters:
 *
 * parent - Optional item returned by <addItem>.
 * force - Optional boolean to ignore <smartSeparators>. Default is false.
 */
mxPopupMenu.prototype.addSeparator = function(parent, force)
{
    parent = parent || this;

    if (this.smartSeparators && !force)
    {
        parent.willAddSeparator = true;
    }
    else if (parent.tbody != null)
    {
        parent.willAddSeparator = false;
        var tr = document.createElement('tr');

        var col1 = document.createElement('td');
        col1.className = 'mxPopupMenuIcon';
        col1.style.padding = '0 0 0 0px';

        tr.appendChild(col1);

        var col2 = document.createElement('td');
        col2.style.padding = '0 0 0 0px';
        col2.setAttribute('colSpan', '2');

        var hr = document.createElement('hr');
        hr.setAttribute('size', '1');
        col2.appendChild(hr);

        tr.appendChild(col2);

        parent.tbody.appendChild(tr);
    }
};

/**
 * Function: popup
 *
 * Shows the popup menu for the given event and cell.
 *
 * Example:
 *
 * (code)
 * graph.panningHandler.popup = function(x, y, cell, evt)
 * {
 *   mxUtils.alert('Hello, World!');
 * }
 * (end)
 */
mxPopupMenu.prototype.popup = function(x, y, cell, evt)
{
    if (this.div != null && this.tbody != null && this.factoryMethod != null)
    {
        this.div.style.left = x + 'px';
        this.div.style.top = y + 'px';

        // Removes all child nodes from the existing menu
        while (this.tbody.firstChild != null)
        {
            mxEvent.release(this.tbody.firstChild);
            this.tbody.removeChild(this.tbody.firstChild);
        }

        this.itemCount = 0;
        this.factoryMethod(this, cell, evt);

        if (this.itemCount > 0)
        {
            this.showMenu();
            this.fireEvent(new mxEventObject(mxEvent.SHOW));
        }
    }
};

/**
 * Function: isMenuShowing
 *
 * Returns true if the menu is showing.
 */
mxPopupMenu.prototype.isMenuShowing = function()
{
    return this.div != null && this.div.parentNode == document.body;
};

/**
 * Function: showMenu
 *
 * Shows the menu.
 */
mxPopupMenu.prototype.showMenu = function()
{
    // Disables filter-based shadow in IE9 standards mode
    if (document.documentMode >= 9)
    {
        this.div.style.filter = 'none';
    }

    // Fits the div inside the viewport
    document.body.appendChild(this.div);
    mxUtils.fit(this.div);
};

/**
 * Function: hideMenu
 *
 * Removes the menu and all submenus.
 */
mxPopupMenu.prototype.hideMenu = function()
{
    if (this.div != null)
    {
        if (this.div.parentNode != null)
        {
            this.div.parentNode.removeChild(this.div);
        }

        this.hideSubmenu(this);
        this.containsItems = false;
        this.fireEvent(new mxEventObject(mxEvent.HIDE));
    }
};

/**
 * Function: hideSubmenu
 *
 * Removes all submenus inside the given parent.
 *
 * Parameters:
 *
 * parent - An item returned by <addItem>.
 */
mxPopupMenu.prototype.hideSubmenu = function(parent)
{
    if (parent.activeRow != null)
    {
        this.hideSubmenu(parent.activeRow);

        if (parent.activeRow.div.parentNode != null)
        {
            parent.activeRow.div.parentNode.removeChild(parent.activeRow.div);
        }

        parent.activeRow = null;
    }
};

/**
 * Function: destroy
 *
 * Destroys the handler and all its resources and DOM nodes.
 */
mxPopupMenu.prototype.destroy = function()
{
    if (this.div != null)
    {
        mxEvent.release(this.div);

        if (this.div.parentNode != null)
        {
            this.div.parentNode.removeChild(this.div);
        }

        this.div = null;
    }
};
